using System;
using System.Collections.Generic;
using System.IO;
#if net45
using System.Threading.Tasks;
#endif

namespace PentaBiz.Collections
{
    /// <summary>
    /// Provides object pool functionality. 
    /// Wrapped Object should not be immutable...
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public sealed class ObjectPool<T> : Disposable
    {
        private readonly Func<T> _objectGenerator;
        private readonly Queue<T> _objects = new Queue<T>();

        /// <summary>
        /// Initializes a new instance of the <see cref="ObjectPool{T}" /> class.
        /// </summary>
        /// <param name="objectGenerator">The object generator.</param>
        /// <param name="initialPoolSize">The number of initial objects.</param>
        /// <param name="maximumPoolSize">The maximum size of the Pool </param>
        /// <exception cref="System.ArgumentNullException">objectGenerator</exception>
        public ObjectPool(Func<T> objectGenerator, int initialPoolSize = 0, int? maximumPoolSize = null)
        {
            if (objectGenerator == null)
                throw new ArgumentNullException("objectGenerator");

            if (maximumPoolSize.HasValue && maximumPoolSize < initialPoolSize)
                throw new ArgumentOutOfRangeException("maximumPoolSize", maximumPoolSize, "Maximum Pool size cannot be less than initialPoolSize if specified");

            _objectGenerator = objectGenerator;


            //create initial objects in the pool
            for (int i = 0; i < initialPoolSize; i++)
            {
                PutObject(_objectGenerator());
            }
        }


        /// <summary>
        /// Gets the object.
        /// </summary>
        /// <param name="initializeObject">The object re initializer.</param>
        /// <returns></returns>
        public ObjectPoolItem<T> GetObject(Action<T> initializeObject = null)
        {
            T instance = default(T);
            lock (_objects)
            {
                if (_objects.Count > 0)
                    instance = _objects.Dequeue();
            }
            if (Equals(instance, null))
                instance = _objectGenerator();

            if (initializeObject != null)
                initializeObject(instance);

            return new ObjectPoolItem<T>(this, instance);
        }


        /// <summary>
        /// Puts the object.
        /// </summary>
        /// <param name="item">The item.</param>
        public void PutObject(T item)
        {
            lock (_objects)
            {
                _objects.Enqueue(item);
            }

            if (_maximumPoolSize != null && _objects.Count > _maximumPoolSize)
            {
                lock (this)
                {
                    while (_objects.Count > _maximumPoolSize)
                    {
                        T o;
                        lock (_objects)
                        {
                            if (_objects.Count > 0)
                                o = _objects.Dequeue();
                        }
                    }
                }
            }
        }

        protected override void OnDisposing()
        {
            while ((_objects.Count > 0))
            {
                T instance;
                lock (_objects)
                {
                    instance = _objects.Dequeue();
                }


                var stream = instance as Stream;
                if (stream != null)
                    stream.Close();

                var disposable = instance as IDisposable;
                if (disposable != null)
                {
                    disposable.Dispose();
                }
            }
            base.OnDisposing();
        }

#if net45
        /// <summary>
        /// Puts the object async.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public async Task PutObjectAsync(T item)
        {

            await Task.Run(() => PutObject(item));

        }
#endif

        private int? _maximumPoolSize;
        /// <summary>
        /// Gets the size of the pool.
        /// </summary>
        /// <value>
        /// The size of the pool.
        /// </value>
        public int PoolSize { get { return _objects.Count; } }



        /// <summary>
        /// Gets or sets the maximum size of the pool.
        /// </summary>
        /// <value>
        /// The maximum size of the pool.
        /// </value>
        public int? MaximumPoolSize
        {
            get { return _maximumPoolSize; }
            set { _maximumPoolSize = value; }
        }
    }
}